package com.iwdnb.gkgz.application.strategy;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

import cn.hutool.core.date.DateUtil;
import com.iwdnb.gkgz.application.model.request.AddStrategyTradeStockDataRequest;
import com.iwdnb.gkgz.common.model.dto.StockDayData;
import com.iwdnb.gkgz.common.model.dto.StrategyTradeDTO;
import com.iwdnb.gkgz.common.quota.BollingerBands;
import com.iwdnb.gkgz.common.quota.CCIIndicator;
import com.iwdnb.gkgz.common.quota.MacdIndicator.Macd;
import com.iwdnb.gkgz.common.quota.MacdRelative;
import com.iwdnb.gkgz.common.quota.RSIIndicator;
import com.iwdnb.gkgz.common.quota.VolumeRatioIndicator;
import com.iwdnb.gkgz.common.quota.WRIndicator;
import com.iwdnb.gkgz.common.utils.BigDecimalUtils;
import com.iwdnb.gkgz.common.utils.DateUtils;
import com.iwdnb.gkgz.common.utils.StockUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import static com.iwdnb.gkgz.common.utils.StockUtils.getAveragePrice;

/**
 * macd结合布林下轨买入策略
 *
 * 收益如下：
 * [
 *   {
 *     "股票数": 930,
 *     "总盈利": 6013.68,
 *     "最高总盈利": 11326.12,
 *     "交易次数": 1147,
 *     "持有天数": 8217,
 *     "盈利总额": 6992.84,
 *     "盈利次数": 794,
 *     "盈利持有天数": 6647,
 *     "亏损总额": -979.16,
 *     "亏损次数": 353,
 *     "亏损持有天数": 1570
 *   }
 * ]
 */
@Service
@Slf4j
public class MacdBollingLowStockBackValidateService extends CommmonStockBackValidateService {

    private static final String STATEGY_CODE = "macdBollingLow";

    private static final String SUMMARY =
        "当日60日均价=%s,开盘价=%s,收盘价=%s,60日均线对比:%s-%s-%s,60走势:%s,30日均线对比:%s-%s-%s,30走势:%s,40天均线:%s-%s,20天均线:%s-%s,"
            + "10天均线:%s-%s";
    private static final BigDecimal fuHundredSeventy = new BigDecimal(-170);
    private static BigDecimal onePointFour = new BigDecimal(1.4);
    private static BigDecimal onePoineNine = new BigDecimal(1.9);
    private static BigDecimal fuZeroPoineFour = new BigDecimal(-0.4);
    private static BigDecimal fuSix = new BigDecimal(-6);

    @Override
    public List<StrategyTradeDTO> queryBackValidateDateList(String code, String strategy,
        List<StockDayData> stockDayDataList, AddStrategyTradeStockDataRequest request) {

        MacdRelative.calculateMACD(stockDayDataList);
        VolumeRatioIndicator.calculateVolumeRatio(stockDayDataList);
        BollingerBands.calculateBollingerBands(stockDayDataList);
        RSIIndicator.calculateRSI(stockDayDataList);
        CCIIndicator.calculateCCI(stockDayDataList);
        WRIndicator.calculateWR(stockDayDataList);
        List<String> dateList = new ArrayList<>();
        List<StrategyTradeDTO> strategyTradeDTOList = new ArrayList<>();

        for (int i = 0; i < stockDayDataList.size(); i++) {
            if (i < 700) {
                continue;
            }
            StockDayData data = stockDayDataList.get(i);
            StockDayData beforeOneData = stockDayDataList.get(i - 1);
            StockDayData beforeTwoData = stockDayDataList.get(i - 2);

            if (BigDecimalUtils.isLe(data.getOpenPrice(), BigDecimal.ONE)) {
                continue;
            }
            String date = data.getDate();
            //5日以内有股价在布林线下轨以下
            boolean flag = isBollingLowInFiveDays(stockDayDataList, i);
            if (!flag) {
                continue;
            }

            Macd twoMacd = stockDayDataList.get(i - 2).getMacd();
            Macd oneMacd = beforeOneData.getMacd();
            Macd macd = data.getMacd();
            if (BigDecimalUtils.isGe(twoMacd.getBar(), BigDecimal.ZERO) || BigDecimalUtils.isGe(oneMacd.getBar(),
                BigDecimal.ZERO) || BigDecimalUtils.isGe(macd.getBar(), BigDecimal.ZERO)) {
                continue;
            }
            //股价还在下跌,但是macd量柱缩小
            BigDecimal oneMacdRate = BigDecimalUtils.subStractAndDividePrecent(oneMacd.getBar(), twoMacd.getBar());
            BigDecimal macdRate = BigDecimalUtils.subStractAndDividePrecent(macd.getBar(), oneMacd.getBar());
            //前一日股价下跌,跳过
            if (BigDecimalUtils.isLt(beforeOneData.getClosePrice(), beforeTwoData.getClosePrice())) {
                continue;
            }
            //股价上涨,跳过
            if (BigDecimalUtils.isGt(data.getClosePrice(), beforeOneData.getClosePrice())) {
                continue;
            }
            //前一天量柱缩小或者当天量柱放大，跳过
            if (BigDecimalUtils.isGe(oneMacdRate, BigDecimal.ZERO) || BigDecimalUtils.isLt(macdRate, BigDecimal.ZERO)) {
                continue;
            }
            //股价与250天内最低价相差50%以内
            BigDecimal lowPrice = StockUtils.getMinPriceBeforeDate(stockDayDataList, date, 250);
            BigDecimal lowRate = BigDecimalUtils.subStractAndDividePrecent(beforeTwoData.getClosePrice(), lowPrice);
            if (BigDecimalUtils.isGe(lowRate, fifty)) {
                continue;
            }
            //股价与250天内最高价相差20%以内
            BigDecimal highPrice = StockUtils.getMaxPriceBeforeDate(stockDayDataList, date, 250);
            BigDecimal highRate = BigDecimalUtils.subStractAndDividePrecent(highPrice, beforeTwoData.getClosePrice());
            if (BigDecimalUtils.isLe(highRate, twenty)) {
                continue;
            }
            //cci值小于-170
            BigDecimal cci = StockUtils.getStockQuota(beforeTwoData, CCIIndicator.QUOTA_NAME);
            if (BigDecimalUtils.isGe(cci, fuHundredSeventy)) {
                continue;
            }
            //rsi值小于35
            BigDecimal rsi = StockUtils.getStockQuota(beforeTwoData, RSIIndicator.QUOTA_NAME);
            if (BigDecimalUtils.isGe(rsi, thirtyFive)) {
                continue;
            }
            //wr小于80
            BigDecimal wr = StockUtils.getStockQuota(beforeTwoData, WRIndicator.QUOTA_NAME);
            if (BigDecimalUtils.isLe(wr, eighty)) {
                continue;
            }
            //量比volumeRatio:第一天的1.4倍小于第2天，第2天大于第3天
            BigDecimal beforeTwoVolumeRatio = StockUtils.getStockQuota(beforeTwoData, VolumeRatioIndicator.QUOTA_NAME);
            BigDecimal beforeOneVolumeRatio = StockUtils.getStockQuota(beforeOneData, VolumeRatioIndicator.QUOTA_NAME);
            BigDecimal volumeRatio = StockUtils.getStockQuota(data, VolumeRatioIndicator.QUOTA_NAME);
            //第一天的1.4倍小于第2天
            if (BigDecimalUtils.isLe(beforeTwoVolumeRatio.multiply(onePointFour), beforeOneVolumeRatio)) {
                continue;
            }
            //第2天大于第3天
            if (BigDecimalUtils.isLe(beforeOneVolumeRatio, volumeRatio)) {
                continue;
            }
            //第二天涨幅要大于1.9
            if (BigDecimalUtils.isLt(beforeOneData.getRate(), onePoineNine)) {
                continue;
            }
            //过滤:第三天跌幅接近0(>-0.4),第一天跌幅>-6
            if (BigDecimalUtils.isGt(beforeTwoData.getRate(), fuSix)
                && BigDecimalUtils.isGt(data.getRate(), fuZeroPoineFour)) {
                continue;
            }
            if (!dateList.contains(date)) {
                dateList.add(date);
                StrategyTradeDTO strategyTradeDTO = new StrategyTradeDTO();
                strategyTradeDTO.setCode(code);
                strategyTradeDTO.setUuid(STATEGY_CODE);
                strategyTradeDTO.setBuyDate(DateUtil.parseDate(date));
                strategyTradeDTO.setBuyPrice(data.getClosePrice());
                strategyTradeDTO.setStatus("buy");
                strategyTradeDTO.setV1(rsi);
                strategyTradeDTO.setV2(cci);
                strategyTradeDTO.setV3(wr);
                strategyTradeDTO.setV4(beforeTwoVolumeRatio);
                strategyTradeDTO.setV5(beforeOneVolumeRatio);
                strategyTradeDTO.setV6(volumeRatio);
                strategyTradeDTO.setV7(beforeTwoData.getRate());
                strategyTradeDTO.setV8(beforeOneData.getRate());
                strategyTradeDTO.setV9(data.getRate());

                strategyTradeDTOList.add(strategyTradeDTO);
                //caculateAfterDaysProfit(code, afterTwoDate, stockDayDataList, 10);

            }
        }
        return strategyTradeDTOList;
    }

    /**
     * 5日以内有股价在布林线下轨以下
     *
     * @param stockDayDataList
     * @param index
     * @return
     */
    private boolean isBollingLowInFiveDays(List<StockDayData> stockDayDataList, int index) {
        for (int i = 1; i <= 5; i++) {
            StockDayData data = stockDayDataList.get(index - i);
            boolean flag = BigDecimalUtils.isLe(data.getClosePrice(), data.getBollingerBand().getLowBand());
            if (flag) {
                return true;
            }
        }
        return false;
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void doBackValidate(String code, String strategy, List<StockDayData> stockDayDataList,
        List<StrategyTradeDTO> strategyTradeDTOList) {
        List<String> dateList = strategyTradeDTOList.stream().map(o -> DateUtil.formatDate(o.getBuyDate())).collect(
            Collectors.toList());
        Date latestEndDate = DateUtil.parseDate(dateList.get(0));
        latestEndDate = DateUtil.offsetDay(latestEndDate, -1);
        for (StrategyTradeDTO strategyTradeDTO : strategyTradeDTOList) {
            Date d = strategyTradeDTO.getBuyDate();
            if (DateUtils.beforeOrEquals(d, latestEndDate)) {
                log.debug("{}-{}在上一个周期内,忽略", code, d);
                continue;
            }
            getTradeInfo(code, stockDayDataList, strategyTradeDTO);
            latestEndDate = strategyTradeDTO.getSellDate();
        }
    }

    private void getTradeInfo(String code, List<StockDayData> stockDayDataList, StrategyTradeDTO strategyTradeDTO) {
        String date = DateUtil.formatDate(strategyTradeDTO.getBuyDate());
        //log.debug("{}-{}>>>>>股票信息:{}", code, date, strategyTradeDTO.getSummary());
        StockDayData buyDayData = StockUtils.getStockDayData(stockDayDataList, date);
        BigDecimal buyPrice = buyDayData.getClosePrice();
        int size = 10;
        List<StockDayData> subList = StockUtils.getStockDayDataListAfterDate(stockDayDataList, date, size);
        if (CollectionUtils.isEmpty(subList) || subList.size() < size) {
            return;
        }
        BigDecimal closeRate = null;
        String sellDate = null;
        String info = "";
        BigDecimal sellPrice = null;
        BigDecimal rate = closeRate;
        int holdIndex = 0;
        BigDecimal rangeMaxPrice = strategyTradeDTO.getBuyPrice();
        BigDecimal rangeMaxRate = BigDecimal.ZERO;
        BigDecimal rangeCloseMaxPrice = strategyTradeDTO.getBuyPrice();
        BigDecimal rangeCloseMaxRate = BigDecimal.ZERO;
        BigDecimal minPrice = strategyTradeDTO.getRealtimePrice();
        BigDecimal fuThree = new BigDecimal(-3);
        BigDecimal fuFive = new BigDecimal(-5);
        for (int i = 0; i < size; i++) {
            StockDayData data = subList.get(i);
            StockDayData yesterdayDayData = i == 0 ? data : subList.get(i - 1);
            BigDecimal closePrice = data.getClosePrice();
            closeRate = BigDecimalUtils.subStractAndDividePrecent(closePrice, buyPrice);
            sellDate = data.getDate();
            sellPrice = data.getClosePrice();
            holdIndex = i;
            rangeMaxPrice = BigDecimalUtils.isGt(data.getMaxPrice(), rangeMaxPrice) ? data.getMaxPrice()
                : rangeMaxPrice;
            rangeMaxRate = BigDecimalUtils.subStractAndDividePrecent(rangeMaxPrice, buyPrice);
            rangeCloseMaxPrice = BigDecimalUtils.isGt(data.getClosePrice(), rangeCloseMaxPrice) ? data.getClosePrice()
                : rangeCloseMaxPrice;
            rangeCloseMaxRate = BigDecimalUtils.subStractAndDividePrecent(rangeCloseMaxPrice, buyPrice);
            BigDecimal realtimeRate = BigDecimalUtils.subStractAndDividePrecent(data.getClosePrice(), buyPrice);
            BigDecimal upRange = data.getMaxPrice().subtract(buyPrice);
            BigDecimal upRate = BigDecimalUtils.divideToPrecent(upRange, buyPrice);
            BigDecimal openRange = data.getOpenPrice().subtract(buyPrice);
            BigDecimal openRate = BigDecimalUtils.divideToPrecent(openRange, buyPrice);
            rate = closeRate;
            String sellInfo = "收盘价卖出";

            if (BigDecimalUtils.isGe(realtimeRate, new BigDecimal(120))) {
                Integer yesterdayTradeNumTenTimes = yesterdayDayData.getTradeNum() * 10;
                if (data.getTradeNum() >= yesterdayTradeNumTenTimes) {
                    sellInfo = "出现十倍信号量,止盈";
                    log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo,
                        rate, openRate, closeRate, upRate);
                    info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                        + ",最高点收益=" + upRate;
                    break;
                }
            }
            //首日跌3个点以上，直接卖出
            if (i == 0 && BigDecimalUtils.isLt(data.getRate(), fuThree)) {
                sellInfo = "首日跌超过3个点,卖出";
                log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo, rate,
                    openRate, closeRate, upRate);
                info += "第" + (i + 1) + "天-" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                    + ",最高点收益=" + upRate;
                break;
            }
            //跌5个点，跑路
            if (BigDecimalUtils.isLt(closeRate, fuFive)) {
                sellInfo = "跌过5个点,止损";
                log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo, rate,
                    openRate, closeRate, upRate);
                info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                    + ",最高点收益=" + upRate;
                break;
            }

            //连跌3天，如果前面是涨停板就等跌破涨停板低点卖出
            if (i >= 2) {
                StockDayData beforeTwoData = subList.get(i - 2);
                StockDayData beforeOneData = subList.get(i - 1);
                if (BigDecimalUtils.isLe(data.getRate(), BigDecimal.ZERO) && BigDecimalUtils.isLe(
                    beforeOneData.getRate(), BigDecimal.ZERO) && BigDecimalUtils.isLe(beforeTwoData.getRate(),
                    BigDecimal.ZERO)) {
                    sellInfo = "连跌3天,止损";
                    log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo,
                        rate, openRate, closeRate, upRate);
                    info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                        + ",最高点收益=" + upRate;
                    break;
                }
            }

            //涨过40个点，直接卖出
            if (BigDecimalUtils.isGe(closeRate, new BigDecimal(40))) {
                sellInfo = "涨过40个点,卖掉";
                log.debug("{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", code, data.getDate(), sellInfo, rate, openRate,
                    closeRate, upRate);
                info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                    + ",最高点收益=" + upRate;
                break;
            }
            //5天内涨过35个点
            if (i <= 4 && !StockUtils.isLimitUp(data) && BigDecimalUtils.isGe(closeRate, new BigDecimal(35))) {
                sellInfo = "4天涨过30个点,卖掉";
                log.debug("{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", code, data.getDate(), sellInfo, rate, openRate,
                    closeRate, upRate);
                info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                    + ",最高点收益=" + upRate;
                break;
            }
            //5天还没涨过5个点
            if (i == 5 && BigDecimalUtils.isLt(closeRate, new BigDecimal(3))) {
                sellInfo = "6天没涨过3个点,卖掉";
                log.debug("{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", code, data.getDate(), sellInfo, rate, openRate,
                    closeRate, upRate);
                info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                    + ",最高点收益=" + upRate;
                break;
            }

            //20个点以上，回落到10个点
            if (BigDecimalUtils.isGe(rangeCloseMaxRate, new BigDecimal(20)) && BigDecimalUtils.isLe(
                realtimeRate.multiply(new BigDecimal(2)), rangeCloseMaxRate)) {
                sellInfo = "20个点以上回落到10个点左右,止盈";
                log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo, rate,
                    openRate, closeRate, upRate);
                info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                    + ",最高点收益=" + upRate;
                break;
            }
            //冲高回落
            if (BigDecimalUtils.isGe(rangeCloseMaxRate, new BigDecimal(7)) && BigDecimalUtils.isLe(realtimeRate,
                new BigDecimal(3))) {
                sellInfo = "冲高回落,卖出";
                log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo, rate,
                    openRate, closeRate, upRate);
                info += "第" + (i + 1) + "天-" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                    + ",最高点收益=" + upRate;
                break;
            }

            //15日内跌停
            if (i < 15 && StockUtils.isLimitDown(data)) {
                sellInfo = "跌停,卖出";
                log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo, rate,
                    openRate, closeRate, upRate);
                info += "第" + (i + 1) + "天-" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                    + ",最高点收益=" + upRate;
                break;
            }

            if (i == size - 1) {
                log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo, rate,
                    openRate, closeRate, upRate);
                info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                    + ",最高点收益=" + upRate;
                break;
            }

        }
        strategyTradeDTO.setHoldDay(holdIndex + 1);
        strategyTradeDTO.setSellDate(DateUtil.parseDate(sellDate));
        strategyTradeDTO.setSellPrice(sellPrice);
        strategyTradeDTO.setProfitRate(rate);
        strategyTradeDTO.setRangeMaxPrice(rangeMaxPrice);
        strategyTradeDTO.setRangeMaxRate(BigDecimalUtils.subStractAndDividePrecent(rangeMaxPrice, buyPrice));
        strategyTradeDTO.setStrategyDescription(info);
        strategyTradeDTO.setStatus("sell");
        strategyTradeDTO.setWinFlag(BigDecimalUtils.isGt(rate, BigDecimal.ZERO) ? "y" : "n");
    }

}
